package main;

import java.awt.Point;
import java.awt.event.ActionEvent;
import java.util.*;
import View.LotusViewer;

import Game.AbstractBehavior;
import Game.AbstractGame;
import Game.AbstractLevel;
import Game.AbstractSprite;

/**
 * 
 * @author Chris Kim
 *
 */
public class LotusEngine extends Observable 
	implements java.awt.event.ActionListener
{
	private AbstractGame myGame;
	private AbstractLevel[] myLevels;
	private AbstractLevel myLevel;
	private int myCurrentLevelIndex;
	private AbstractSprite[] myInitialSprites;
	private ArrayList<String[]> myCollisions;
	private String[] mySpriteTypes;
	private Map<String, ArrayList<AbstractSprite>> mySpriteArrays;
	
    // make it easy to refer to one second in milliseconds
    private static final int ONE_SECOND = 1000;
    // make it easy to change animation rate
    private static final int NUM_FRAMES_PER_SECOND = 40;

    // connect the model and the view
    private javax.swing.Timer myClock;
    private int myIdealFrameRate;
    private int myActualFrameRate;
    private int myNumSteps;
    private double myElapsedTime;
    private long myStartTime;
    private long myCurrentTime;
    private LotusViewer myViewer;
	
	public LotusEngine()
	{
		myCurrentLevelIndex = 0;
		setView(new LotusViewer(this));
	}
	
	public void setView(LotusViewer v)
	{
	    myViewer = v;
	}
	
    public int getFrameRate ()
    {
        return myIdealFrameRate;
    }

    public int getActualFrameRate ()
    {
        return myActualFrameRate;
    }

    public double getCurrentTime ()
    {
        return toSeconds(myCurrentTime - myStartTime);
    }
    
    //------------------------------------------------------------
    // conversion functions
    protected double toSeconds (long milliSeconds)
    {
        return ((double)milliSeconds) / ONE_SECOND;
    }

    protected int toMilliSeconds (double seconds)
    {
        return (int)(seconds * ONE_SECOND);
    }
    //
    //------------------------------------------------------------


    public void reset ()
    {
        myStartTime = 0;
        myCurrentTime = 0;
        myElapsedTime = 0;
        myNumSteps = 0;
        myClock = null;
    }
    
    private void start()
    {
        if (getCurrentTime() == 0)
        {
            onStart();
        }
        myClock = new javax.swing.Timer(ONE_SECOND / myIdealFrameRate, this);
        myClock.start();
        myCurrentTime = myStartTime = System.currentTimeMillis();
    }
	
	private void playGame()
	{
		loadLevel(myCurrentLevelIndex);
		for(AbstractSprite s: myInitialSprites)
		{
			showSprite(s);
		}
		while(!endGame)
		{
			updateSpritePositions();
			checkCollisions();
			if(myLevel.levelClear())
			{
				myCurrentLevelIndex++;
				loadLevel(myCurrentLevelIndex);
			}
			if(myGame.winGame())
				endGame = true;
			else if (myGame.loseGame())
				endGame = true;
		}
	}
	
	public void loadGame (AbstractGame game)
	{
		myGame = game;
		myLevels = myGame.getLevelArray();
		mySpriteTypes = myGame.getSpriteArray();
		mySpriteArrays = new HashMap<String, ArrayList<AbstractSprite>>();
		for(String s: mySpriteTypes)
		{
			mySpriteArrays.put(s, new ArrayList<AbstractSprite>());
		}
	}
	
	private void loadLevel(int n)
	{
		reset();
		myLevel = myLevels[n];
		myInitialSprites = myLevel.getSpriteCollection();
		myCollisions = (ArrayList<String[]>) myLevel.getSpriteCollisionPairs();
		for(AbstractSprite a: myInitialSprites)
		{
			mySpriteArrays.get(a.getType()).add(a);
		}
		myCurrentLevelIndex = n;
	}
	
	private void checkCollisions()
	{
		for(String[] a: myCollisions)
		{
			ArrayList<AbstractSprite> u = mySpriteArrays.get(a[0]);
			ArrayList<AbstractSprite> v = mySpriteArrays.get(a[1]);
			for(AbstractSprite x: u)
			{
				for(AbstractSprite y: v)
				{
					if(y.getPosition().equals(x.getPosition()))
					{
						myGame.collision(x, y);
						myLevel.collision(x, y);
						x.collisionReaction(y);
						y.collisionReaction(x);
					}
				}
			}
		}
	}
	
	public void addSprite(AbstractSprite sprite)
	{
		mySpriteArrays.get(sprite.getType()).add(sprite);
	}
	
	public void removeSprite(AbstractSprite sprite)
	{
		mySpriteArrays.get(sprite.getType()).remove(sprite);
	}
	
	private void updateSpritePositions()
	{
		for(String s: mySpriteArrays.keySet())
		{
			ArrayList<AbstractSprite> tempList = mySpriteArrays.get(s);
			if(!tempList.isEmpty())
			{
				for(AbstractSprite temp: tempList)
				{
					for(AbstractBehavior behavior: temp.getBehaviors())
					{
						behavior.step(temp);
					}
				}
			}
		}
	}

	@Override
	public void actionPerformed(ActionEvent e) {
        long lastTime = myCurrentTime;
        myCurrentTime = System.currentTimeMillis();
        doStep(toSeconds(myCurrentTime - lastTime));
	}
}